//
//  AppDelegate.m
//  Crash
//
//  Created by 周成龙 on 2019/5/7.
//  Copyright © 2019 周成龙. All rights reserved.
//

#import "AppDelegate.h"
#include <execinfo.h>
#import <CrashReporter/CrashReporter.h>

@interface AppDelegate ()

@property (nonatomic, assign) UIBackgroundTaskIdentifier backgroundTaskIdentifier;
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, assign) NSInteger second;

/// 卡顿监控
@property (nonatomic, assign)  int timeoutCount;
@property (nonatomic, assign) CFRunLoopObserverRef observer;
@property (nonatomic, strong) dispatch_semaphore_t semaphore;
@property (nonatomic, assign) CFRunLoopActivity activity;

@end


static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    AppDelegate *moniotr = (__bridge AppDelegate *)info;
    moniotr.activity = activity;
    
    dispatch_semaphore_t semaphore = moniotr.semaphore;
    dispatch_semaphore_signal(semaphore);
}

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    self.second = 0;
    [self startSignalMonitor];
    [self startThreadMonitor];
    
    return YES;
}

#pragma mark - 注册信号

- (void)startSignalMonitor {
    //捕捉crash类型的错误
    NSSetUncaughtExceptionHandler(&CrashExceptionHandler);
    
    //捕捉sinal类型的错误
    //SIGSEGV segmentation violation 内存段异常
    //试图访问未分配给自己的内存，或试图往没有写权限的内存地址写数据。
    signal(SIGSEGV, SignalExceptionHandler);
    //SIGFPE floating point exception 算数运算错误
    signal(SIGFPE, SignalExceptionHandler);
    //SIGBUS bus error 非法地址
    signal(SIGBUS, SignalExceptionHandler);
    //SIGPIPE write on a pipe with no one to read it
    //管道破裂，进程间通信产生
    signal(SIGPIPE, SignalExceptionHandler);
    //SIGHUP hangup
    //本信号在用户终端连接(正常或非正常)结束时发出，通常是在终端的控制进程结束时
    signal(SIGHUP, SignalExceptionHandler);
    //SIGINT interrupt 程序终止(interrupt)信号
    signal(SIGINT, SignalExceptionHandler);
    //SIGQUIT quit 程序终止(quit)信号
    signal(SIGQUIT, SignalExceptionHandler);
    //SIGABRT abort() 调用abort函数生成的信号
    signal(SIGABRT, SignalExceptionHandler);
    //SIGILL illegal instruction (not reset when caught) 执行了非法指令
    signal(SIGILL, SignalExceptionHandler);
}

#pragma mark - 处理异常

void SignalExceptionHandler(int signal) {
    NSString *callStack = [AppDelegate backtrace];
    NSString *name = @"SignalException";
    NSString *reson = [NSString stringWithFormat:@"signal %d was raised", signal];
    NSString *descriptionStr = [NSString stringWithFormat:@"名字：%@ \n原因：%@\n信号捕获崩溃，堆栈信息：%@ \n", name, reson, callStack];
    
    NSLog(@"%@", descriptionStr);
    //TODO:上传Exception信息
    [AppDelegate saveSigCrash:descriptionStr];
}

+ (NSString *)backtrace {
    //指针列表
    void* callstack[128];
    //backtrace 用来获取当前线程的调用堆栈，获取的信息存放在这里的 callstack 中
    //128用来指定当前的 buffer 中可以保存多少个 void* 元素
    //返回值是实际获取的指针个数
    int frames = backtrace(callstack, 128);
    //backtrace_symbols 将从 backtrace 函数获取的信息转化为一个字符串数组
    //返回一个指向字符串数组的指针
    //每个字符串包含了一个相对于 callstack 中对应元素的可打印信息，包括函数名、偏移地址、实际返回地址
    char** traceChar = backtrace_symbols(callstack, frames);
    NSMutableString *crashString = [[NSMutableString alloc] init];
    for (int i = 0; i <frames; ++i) {
        [crashString appendFormat:@"%s\n", traceChar[i]];
    }
    free(traceChar);
    return crashString;
}

void CrashExceptionHandler(NSException *exception) {
    NSArray *callStack = [exception callStackSymbols];
    NSString *reson = [exception reason];
    NSString *name = [exception name];
    
    NSString *descriptionStr = [NSString stringWithFormat:@"名字：%@ \n原因：%@\n exception捕获崩溃，堆栈信息：%@ \n", name, reson, callStack];
    NSLog(@"%@", descriptionStr);
    //TODO:上传Exception信息
    [AppDelegate saveOCCrash:descriptionStr];
}

#pragma mark - 本地化

+ (void)saveSigCrash:(NSString *)exceptionInfo {
    NSString * _libPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"SigCrash"];
    if (![[NSFileManager defaultManager] fileExistsAtPath:_libPath]) {
        [[NSFileManager defaultManager] createDirectoryAtPath:_libPath withIntermediateDirectories:YES attributes:nil error:nil];
    }
    
    NSDate* dat = [NSDate dateWithTimeIntervalSinceNow:0];
    NSTimeInterval a=[dat timeIntervalSince1970];
    NSString *timeString = [NSString stringWithFormat:@"%f", a];
    
    NSString * savePath = [_libPath stringByAppendingFormat:@"/error%@.log",timeString];
    
    BOOL sucess = [exceptionInfo writeToFile:savePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
    
    NSLog(@"YES sucess:%d",sucess);
}

+ (void)saveOCCrash:(NSString *)exceptionInfo {
    NSString * _libPath  = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"OCCrash"];
    if (![[NSFileManager defaultManager] fileExistsAtPath:_libPath]){
        [[NSFileManager defaultManager] createDirectoryAtPath:_libPath withIntermediateDirectories:YES attributes:nil error:nil];
    }
    
    NSDate* dat = [NSDate dateWithTimeIntervalSinceNow:0];
    NSTimeInterval a=[dat timeIntervalSince1970];
    NSString *timeString = [NSString stringWithFormat:@"%f", a];
    
    NSString * savePath = [_libPath stringByAppendingFormat:@"/error%@.log",timeString];
    
    BOOL sucess = [exceptionInfo writeToFile:savePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
    
    NSLog(@"YES sucess:%d",sucess);
}

#pragma mark - 后台任务
- (void)applicationDidEnterBackground:(UIApplication *)application {
    self.second = 0;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(onTimer) userInfo:nil repeats:YES];
    self.backgroundTaskIdentifier = [application beginBackgroundTaskWithExpirationHandler:^( void) {
        [self myTask:application];
    }];
}

- (void)onTimer {
    self.second += 1;
    NSLog(@"Zhoucl:%ld", (long) self.second);
    if (self.second == 5) {
        [self.timer invalidate];
        self.timer = nil;
        [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
        self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
    }
}

- (void)myTask:(UIApplication *)application {
    NSLog(@"后台任务超时被退出");
    [application endBackgroundTask:self.backgroundTaskIdentifier];//通知系统后台任务已处理完毕
    self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
}

- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
    NSLog(@"applicationDidReceiveMemoryWarning");
}

#pragma mark - 卡顿监控

- (void)stopMonitor {
    if (!self.observer) {
        return;
    }
    CFRunLoopRemoveObserver(CFRunLoopGetMain(), self.observer, kCFRunLoopCommonModes);
    CFRelease(self.observer);
    self.observer = NULL;
}

- (void)startThreadMonitor {
    if (self.observer) {
        return;
    }
    
    // 信号,Dispatch Semaphore保证同步
    self.semaphore = dispatch_semaphore_create(0);
    
    // 注册RunLoop状态观察
    CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
    self.observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                       kCFRunLoopAllActivities,
                                       YES,
                                       0,
                                       &runLoopObserverCallBack,
                                       &context);
    //将观察者添加到主线程runloop的common模式下的观察中
    CFRunLoopAddObserver(CFRunLoopGetMain(), self.observer, kCFRunLoopCommonModes);
    
    // 在子线程监控时长 开启一个持续的loop用来进行监控
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (YES) {
            //假定连续5次超时50ms认为卡顿(当然也包含了单次超时250ms)
            long st = dispatch_semaphore_wait(self.semaphore, dispatch_time(DISPATCH_TIME_NOW, 50 * NSEC_PER_MSEC));
            if (st != 0) {
                if (!self.observer) {
                    self.timeoutCount = 0;
                    self.semaphore = 0;
                    self.activity = 0;
                    return;
                }
                //两个runloop的状态，BeforeSources和AfterWaiting这两个状态区间时间能够检测到是否卡顿
                if (self.activity == kCFRunLoopBeforeSources || self.activity == kCFRunLoopAfterWaiting) {
                    if (++self.timeoutCount < 5) {
                        continue;
                    }
                    // 获取数据
                    PLCrashReporterConfig *config = [[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD
                                                                                       symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll];
                    PLCrashReporter *crashReporter = [[PLCrashReporter alloc] initWithConfiguration:config];
                    NSData *data = [crashReporter generateLiveReport];
                    // 转换成 PLCrashReport 对象
                    PLCrashReport *reporter = [[PLCrashReport alloc] initWithData:data error:NULL];
                    // 进行字符串格式化处理
                    NSString *report = [PLCrashReportTextFormatter stringValueForCrashReport:reporter
                                                                              withTextFormat:PLCrashReportTextFormatiOS];
                    //上传服务器
                    NSLog(@"此处发生卡顿:---%@", report);
                }
            }
            self.timeoutCount = 0;
        }
    });
}

@end

